package com.vpen.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;

/**
 * 描述
 *
 * @author 韦鹏
 * @date 2024/11/17 11:26
 */
@Slf4j
public class GenerateUtil {

    /**
     * 文件目录
     */
    private final static String TEMP_DIR = "tmp";
    private final static String SHA1_WITH_RSA = "SHA1withRSA";
    private final static String CA_DIR = "ca";
    private final static String D_DISH = "D:";
    /**
     * 证书文件名
     */
    private final static String CA_CRT = "ca.crt";
    /**
     * 公钥文件名
     */
    private final static String CA_PUB = "ca.pub";
    /**
     * 私钥文件名字
     */
    private final static String CA_KEY = "ca.key";
    /**
     * power.conf文件名
     * */
    public final static String POWER_CONF = "power.conf";

    public final static String ACTIVE_CODE ="activeCode.txt";

    private static String rootCertVersion1 = "860106576952879101192782278876319243486072481962999610484027161162448933268423045647258145695082284265933019120714643752088997312766689988016808929265129401027490891810902278465065056686129972085119605237470899952751915070244375173428976413406363879128531449407795115913715863867259163957682164040613505040314747660800424242248055421184038777878268502955477482203711835548014501087778959157112423823275878824729132393281517778742463067583320091009916141454657614089600126948087954465055321987012989937065785013284988096504657892738536613208311013047138019418152103262155848541574327484510025594166239784429845180875774012229784878903603491426732347994359380330103328705981064044872334790365894924494923595382470094461546336020961505275530597716457288511366082299255537762891238136381924520749228412559219346777184174219999640906007205260040707839706131662149325151230558316068068139406816080119906833578907759960298749494098180107991752250725928647349597506532778539709852254478061194098069801549845163358315116260915270480057699929968468068015735162890213859113563672040630687357054902747438421559817252127187138838514773245413540030800888215961904267348727206110582505606182944023582459006406137831940959195566364811905585377246353";
    private static String rootCertVersion2 = "24156627931985958051017183040835577271803742470193804806479316178045088981962804168393398987646446251087541768081971475544151551235525470790716604369379805327668466429966167642117961353233058515180243264560201783520956161510523416923017697354365782825500659342029196527776056976223174394946371372849906309277537461992299774200098515526818746947230488275456663264920440977381968978227273889068919338259949793686590492904029279861913225794809675826299753284990778166519152326723946780528965868736869495336993456735232755342913885746267768375682771655854436236934171901662660193080235109535758464079136573948168636773471";
    private static String commonArgsStr = "EQUAL,65537,24773058818499217187577663886010908531303294206336895556072197892590450942803807164562754911175164262596715237551312004078542654996496301487027034803410086499747369353221485073240039340641397198525027728751956658900801359887190562885573922317930300068615009483578963467556425525328780085523172495307229112069939166202511721671904748968934606589702999279663332403655662225374084460291376706916679151764149324177444374590606643838366605181996272409014933080082205048098737253668016260658830645459388519595314928290853199112791333551144805347785109465401055719331231478162870216035573012645710763533896540021550083104281->3,24773058818499217187577663886010908531303294206336895556072197892590450942803807164562754911175164262596715237551312004078542654996496301487027034803410086499747369353221485073240039340641397198525027728751956658900801359887190562885573922317930300068615009483578963467556425525328780085523172495307229112069939166202511721671904748968934606589702999279663332403655662225374084460291376706916679151764149324177444374590606643838366605181996272409014933080082205048098737253668016260658830645459388519595314928290853199112791333551144805347785109465401055719331231478162870216035573012645710763533896540021550083104281";
    private static String licenseId = "FV8EM46DQYC5AW9";


    /**
     * @Author Vpen
     * @Desc 生成激活码
     * @Date 2024/11/17 13:08
     **/
    public static String genLicense(CertificateHolder holder){
        return genLicense(holder,getLicensePart());
    }
    public static String genLicense(CertificateHolder holder,String licensePart){
        return genLicense(holder.getKeyPair().getPrivate(),getCaCrtPath(),licenseId,licensePart);
    }

    public static String genLicense(PrivateKey privateKey,String crtPath,String licenseId,String licensePart){
        try {
            // 创建Signature对象并初始化
            Signature signature = Signature.getInstance(SHA1_WITH_RSA);
            signature.initSign(privateKey);

            // 更新要签名的数据
            signature.update(licensePart.getBytes(StandardCharsets.UTF_8));

            // 生成签名
            byte[] sign = signature.sign();


            String encodedSignature = Base64.getEncoder().encodeToString(sign);
            String licensePartBase64 = Base64.getEncoder().encodeToString(licensePart.getBytes(StandardCharsets.UTF_8));

            String string = FileUtil.readUtf8String(FileUtil.touch(crtPath));
            String certStr = StrUtil.subBetween(string,"-----BEGIN CERTIFICATE-----","-----END CERTIFICATE-----").replaceAll("\\s+", "");

            String result = licenseId + "-" + licensePartBase64 + "-" + encodedSignature + "-" + certStr;

            log.debug("激活码:{}",result);

            File activeCode = FileUtil.touch(getActiveCodePath());
            saveFile(activeCode,result);

            return result;
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }



    /**
     * @Author Vpen
     * @Desc 生成证书
     * @Date 2024/11/17 12:22
     **/
    public static CertificateHolder generateCertificate(String who,Date start, Date end){

        try {
            // 1.生成证书

            Security.addProvider(new BouncyCastleProvider());
            // 生成 RSA 密钥对
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(4096);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            // 颁发者
            X500Name issuer = new X500Name("CN=JetProfile CA");
            // 颁发给
            X500Name subject = new X500Name(who);
            X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, BigInteger.valueOf(System.currentTimeMillis()), start, end, subject, keyPair.getPublic());
            ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC").build(keyPair.getPrivate());
            X509CertificateHolder certHolder = certBuilder.build(contentSigner);
            X509Certificate certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
            return CertificateHolder.build(certificate,keyPair);
        } catch (NoSuchAlgorithmException | OperatorCreationException | CertificateException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * @Author Vpen
     * @Desc 生成power.config
     * @Date 2024/11/17 13:11
     **/
    public static  String  buildPowerConfig(CertificateHolder holder){
        try {
            KeyPair keyPair = holder.getKeyPair();
            X509Certificate certificate = holder.getCertificate();

            PrivateKey privateKey = keyPair.getPrivate();
            PublicKey publicKey = keyPair.getPublic();

            // 获取模长
            int modBits = ((RSAPublicKey) publicKey).getModulus().bitLength();

            MessageDigest digestCert = MessageDigest.getInstance("SHA-256");
            digestCert.update(certificate.getTBSCertificate());

            byte[] digest1 = digestCert.digest();

            log.info("pacs after :{}", Base64.getEncoder().encodeToString(digest1));
            // 使用 PKCS#1.5 编码
            String res = new BigInteger(1, pkcs15Encode(digest1, (modBits + 7) / 8, true)).toString();


            // 定义输出字符串
            StringBuilder power = new StringBuilder("[Result]\n");
            power.append("EQUAL,").append(bytes2BigInt(certificate.getSignature())).append(",65537,").append(rootCertVersion1).append("->").append(res).append("\n");
            power.append("EQUAL,").append(bytes2BigInt(certificate.getSignature())).append(",65537,").append(rootCertVersion2).append("->").append(res).append("\n");
            power.append("[Args]\n").append(commonArgsStr).append("\n");

            // 写入 power.conf 文件
            File powerConf = FileUtil.touch(getPowerConfPath());
            saveFile(powerConf,power.toString());

            return power.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void saveFile(File file,String content){
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            writer.write(content);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void saveFile(String filePath,String content){
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write(content);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Author Vpen
     * @Desc 使用pkcs#1 v1.5填充
     * @Date 2024/11/17 13:00
     **/
    public static byte[] pkcs15Encode(byte[] msgHash, int emLen, boolean withHashParameters) throws Exception {
        // Define the OID for the hash algorithm used (SHA-256)
        ASN1ObjectIdentifier sha256Oid = new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1");

        ASN1EncodableVector digestAlgo = new ASN1EncodableVector();
        digestAlgo.add(sha256Oid);

        if (withHashParameters) {
            digestAlgo.add(DERNull.INSTANCE);
        }


        DEROctetString digest = new DEROctetString(msgHash);
        ASN1EncodableVector asn1List = new ASN1EncodableVector();
        asn1List.add(new DERSequence(digestAlgo));
        asn1List.add(digest);
        DERSequence digestInfo = new DERSequence(asn1List);

        byte[] encodedDigestInfo = digestInfo.getEncoded(ASN1Encoding.DER);

        if (emLen < encodedDigestInfo.length + 11) {
            throw new IllegalArgumentException("Selected hash algorithm has a too long digest.");
        }


        byte[] ps = new byte[emLen - encodedDigestInfo.length - 3];
        Arrays.fill(ps, (byte) 0xFF);


        byte[] result = new byte[emLen];
        result[0] = 0x00;
        result[1] = 0x01;
        System.arraycopy(ps, 0, result, 2, ps.length);
        result[ps.length + 2] = 0x00;
        System.arraycopy(encodedDigestInfo, 0, result, ps.length + 3, encodedDigestInfo.length);

        log.debug("msgHash:{}",Base64.getEncoder().encodeToString(msgHash));
        log.debug("res:{}",Base64.getEncoder().encodeToString(result));

        return result;
    }

    /**
     * 加载私钥  PKCS#1格式的
     * 以 ----BEGIN RSA PRIVATE KEY----- 开头的
     *
     * @Author Vpen
     **/
    public static PrivateKey loadPKCS1PrivateKeyFromFile(String fileName) {
        try {
            // 加载BouncyCastleProvider
            Security.addProvider(new BouncyCastleProvider());
            PEMParser pemParser = new PEMParser(new FileReader(fileName));
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
            Object object = pemParser.readObject();
            KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
            return kp.getPrivate();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Author Vpen
     * @Desc 使用SHA1对内容 签名
     * @Date 2024/11/17 11:32
     **/
    public static byte[] signParam(PrivateKey privateKey, String data) {
        try {
            // 创建Signature对象并初始化
            Signature signature = Signature.getInstance(SHA1_WITH_RSA);
            signature.initSign(privateKey);

            // 更新要签名的数据
            signature.update(data.getBytes(StandardCharsets.UTF_8));

            // 生成签名
            return signature.sign();
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Author Vpen
     * @Desc 使用SHA1对内容 签名 获取签名后的Base64
     * @Date 2024/11/17 11:34
     **/
    public static String signParamAndGetBase64(PrivateKey privateKey, String data) {
        return Base64.getEncoder().encodeToString(signParam(privateKey, data));
    }


    /**
     * @Author Vpen
     * @Desc 将字节数组转为大整数用字符串表示
     * @Date 2024/11/17 11:36
     **/
    public static String bytes2BigInt(byte[] signature) {
        // 将字节数组转换为整数
        // 1 表示数字是正数
        BigInteger number = new BigInteger(1, signature);
        return String.valueOf(number.toString());
    }


    /**
     * @Author Vpen
     * @Desc 验证签名
     * @Date 2024/11/17 11:39
     **/
    public static void verifySignature(PublicKey publicKey, byte[] digest, byte[] signature) throws SignatureException {
        try {
            Signature verifier = Signature.getInstance(SHA1_WITH_RSA);
            verifier.initVerify(publicKey);
            verifier.update(digest);
            if (!verifier.verify(signature)) {
                throw new SignatureException("Signature verification failed");
            }
            log.info("签名验证正确");
        } catch (Exception e) {
            throw new SignatureException("Signature verification failed", e);
        }
    }

    @SneakyThrows
    public static void write2File(KeyPair keyPair, X509Certificate certificate) {
        File cakeyFile = FileUtil.touch(getCaKeyPath());
        File pubFile = FileUtil.touch(getCaPubPath());
        File cetFile = FileUtil.touch(getCaCrtPath());


        // 将私钥写入文件
        try (JcaPEMWriter writer = new JcaPEMWriter(new OutputStreamWriter(new FileOutputStream(cakeyFile)))) {
            try {
                writer.writeObject(keyPair.getPrivate());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try (JcaPEMWriter writer = new JcaPEMWriter(new OutputStreamWriter(new FileOutputStream(pubFile)))) {
            try {
                writer.writeObject(keyPair.getPublic());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 将证书写入文件
        try (JcaPEMWriter writer = new JcaPEMWriter(new OutputStreamWriter(new FileOutputStream(cetFile)))) {
            try {
                writer.writeObject(certificate);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * @Author Vpen
     * @Desc 文件放在哪
     * @Date 2024/11/17 11:57
     **/
    public static String getPath(String fileName) {
        String baseDir = SystemUtil.getOsInfo().isWindows() ? D_DISH  : "";
        //String path = StrUtil.join(File.separator, ".",TEMP_DIR, CA_DIR,  fileName);
        String path = StrUtil.join(File.separator, ".",  fileName);
        log.debug("文件路径: {}", path);
        return path;
    }

    public static String getCaCrtPath() {
        return getPath(CA_CRT);
    }

    public static String getCaPubPath() {
        return getPath(CA_PUB);
    }

    public static String getCaKeyPath() {
        return getPath(CA_KEY);
    }
    public static String getPowerConfPath(){
        return getPath(POWER_CONF);
    }
    public static String getActiveCodePath(){
        return getPath(ACTIVE_CODE);
    }


    public static String getLicensePart(){
       return FileUtil.readUtf8String("LicensePart.json");
    }
}
